﻿namespace Microsoft.SharePoint.Client
{
    using System;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Reflection;
    using System.Xml;

    internal static class DataRetrieval
    {
        private static void CheckSelectExpression(MethodCallExpression exp)
        {
            if (exp.Arguments.Count != 2)
            {
                throw new InvalidQueryExpressionException();
            }
            LambdaExpression expression2 = ExpressionUtility.StripQuotes(exp.Arguments[1]) as LambdaExpression;
            if ((expression2 == null) || (expression2.Parameters.Count != 1))
            {
                throw new InvalidQueryExpressionException();
            }
            ParameterExpression expression4 = ExpressionUtility.StripConverts(ExpressionUtility.StripQuotes(expression2.Body)) as ParameterExpression;
            if ((expression4 == null) || (expression4.Name != expression2.Parameters[0].Name))
            {
                throw new InvalidQueryExpressionException();
            }
        }

        private static bool ExpectConstantExpression_CanExpressionBeEvaluated(Expression exp)
        {
            if (exp != null)
            {
                if (exp.NodeType == ExpressionType.MemberAccess)
                {
                    MemberExpression expression = (MemberExpression) exp;
                    if (!Attribute.IsDefined(expression.Member, typeof(RemoteAttribute)) && !Attribute.IsDefined(expression.Member, typeof(PseudoRemoteAttribute)))
                    {
                        return true;
                    }
                }
                if (((exp.NodeType != ExpressionType.Constant) && (exp.NodeType != ExpressionType.Convert)) && (exp.NodeType != ExpressionType.ConvertChecked))
                {
                    return false;
                }
            }
            return true;
        }

        private static ClientObject GetRootClientObjectForClientQueryableExpression(Expression exp)
        {
            ClientObject obj2 = null;
            ExpressionType nodeType = exp.NodeType;
            if (nodeType != ExpressionType.Call)
            {
                if (nodeType != ExpressionType.Constant)
                {
                    return obj2;
                }
            }
            else
            {
                MethodCallExpression expression = (MethodCallExpression) exp;
                if (expression.Object != null)
                {
                    return GetRootClientObjectForClientQueryableExpression(expression.Object);
                }
                if (expression.Arguments.Count <= 0)
                {
                    throw new InvalidQueryExpressionException();
                }
                return GetRootClientObjectForClientQueryableExpression(expression.Arguments[0]);
            }
            ConstantExpression expression2 = (ConstantExpression) exp;
            obj2 = expression2.Value as ClientObject;
            if (obj2 == null)
            {
                throw new InvalidQueryExpressionException();
            }
            return obj2;
        }

        internal static void Load<T>(T clientObject, params Expression<Func<T, object>>[] retrievals) where T: ClientObject
        {
            ClientObjectCollection objects = clientObject as ClientObjectCollection;
            if ((retrievals == null) || (retrievals.Length == 0))
            {
                clientObject.Query.SelectAllProperties();
                if (objects != null)
                {
                    clientObject.Query.ChildItemQuery.SelectAllProperties();
                }
            }
            else
            {
                QueryProcessInfo queryInfo = new QueryProcessInfo();
                int num = 0;
                foreach (Expression<Func<T, object>> expression in retrievals)
                {
                    QueryMethodAggregator aggregator = new QueryMethodAggregator(expression);
                    ProcessQueryExpression(clientObject.Query, clientObject, expression.Body, true, queryInfo, aggregator);
                    aggregator.Check();
                    if ((aggregator.Where > 0) || (aggregator.Take > 0))
                    {
                        num++;
                        if (num > 1)
                        {
                            throw new InvalidQueryExpressionException();
                        }
                    }
                    if (aggregator.IsEmpty)
                    {
                        clientObject.Query.SelectAllProperties();
                        if (objects != null)
                        {
                            clientObject.Query.ChildItemQuery.SelectAllProperties();
                        }
                    }
                }
                queryInfo.FinalProcess();
            }
        }

        private static ClientQueryInternal ProcessClientQueryableMethodCallQueryExpression(ClientQueryInternal rootQuery, ClientObject rootClientObject, MethodCallExpression exp, bool leaf, QueryProcessInfo queryInfo, QueryMethodAggregator aggregator)
        {
            switch (exp.Method.Name)
            {
                case "Where":
                {
                    ClientQueryInternal internal3 = ProcessQueryExpression(rootQuery, rootClientObject, exp.Arguments[0], leaf, queryInfo, aggregator);
                    aggregator.Where++;
                    LambdaExpression childItemFilterExpression = ExpressionUtility.StripQuotes(exp.Arguments[1]) as LambdaExpression;
                    if (childItemFilterExpression == null)
                    {
                        throw new InvalidQueryExpressionException();
                    }
                    ChunkStringBuilder builder = new ChunkStringBuilder();
                    XmlWriter writer = builder.CreateXmlWriter();
                    writer.WriteStartElement("Where");
                    ClientQueryInternal.WriteFilterExpressionToXml(writer, internal3.ChildItemQuery.ChildItemExpressionSerializationContext, childItemFilterExpression);
                    writer.WriteStartElement("Object");
                    ChunkStringBuilder builder2 = null;
                    if (queryInfo.Expression.TryGetValue(internal3.ChildItemQuery, out builder2))
                    {
                        builder2.WriteContentAsRawXml(writer);
                    }
                    else
                    {
                        writer.WriteStartElement("QueryableObject");
                        writer.WriteEndElement();
                    }
                    writer.WriteEndElement();
                    writer.WriteEndElement();
                    writer.Close();
                    queryInfo.Expression[internal3.ChildItemQuery] = builder;
                    return internal3;
                }
                case "Select":
                {
                    CheckSelectExpression(exp);
                    ClientQueryInternal internal4 = ProcessQueryExpression(rootQuery, rootClientObject, exp.Arguments[0], leaf, queryInfo, aggregator);
                    aggregator.Select++;
                    ClientQueryInternal internal2 = internal4;
                    queryInfo.BySelect[internal4.ChildItemQuery.Id] = internal4.ChildItemQuery;
                    return internal2;
                }
                case "Take":
                {
                    ClientQueryInternal internal5 = ProcessQueryExpression(rootQuery, rootClientObject, exp.Arguments[0], leaf, queryInfo, aggregator);
                    aggregator.Take++;
                    Expression expression3 = exp.Arguments[1];
                    expression3 = ExpressionEvaluator.PartialEvaluate(expression3, new Func<Expression, bool>(DataRetrieval.ExpectConstantExpression_CanExpressionBeEvaluated));
                    if ((expression3.NodeType != ExpressionType.Constant) || (expression3.Type != typeof(int)))
                    {
                        throw new InvalidQueryExpressionException();
                    }
                    ConstantExpression expression4 = (ConstantExpression) expression3;
                    int num = (int) expression4.Value;
                    if (num < 0)
                    {
                        throw new InvalidQueryExpressionException();
                    }
                    ChunkStringBuilder builder3 = new ChunkStringBuilder();
                    XmlWriter writer2 = builder3.CreateXmlWriter();
                    writer2.WriteStartElement("Take");
                    writer2.WriteAttributeString("Count", num.ToString(CultureInfo.InvariantCulture));
                    ChunkStringBuilder builder4 = null;
                    if (queryInfo.Expression.TryGetValue(internal5.ChildItemQuery, out builder4))
                    {
                        builder4.WriteContentAsRawXml(writer2);
                    }
                    else
                    {
                        writer2.WriteStartElement("QueryableObject");
                        writer2.WriteEndElement();
                    }
                    writer2.WriteEndElement();
                    writer2.Close();
                    queryInfo.Expression[internal5.ChildItemQuery] = builder3;
                    return internal5;
                }
            }
            throw new InvalidQueryExpressionException();
        }

        private static ClientQueryInternal ProcessMemberAccessQueryExpression(ClientQueryInternal rootQuery, ClientObject rootClientObject, MemberExpression exp, bool leaf, QueryProcessInfo queryInfo, QueryMethodAggregator aggregator)
        {
            ClientQueryInternal subQuery = null;
            if ((exp.Member.MemberType != MemberTypes.Property) || (exp.Member.GetCustomAttributes(typeof(RemoteAttribute), false).Length <= 0))
            {
                throw new InvalidQueryExpressionException();
            }
            ClientQueryInternal parentQuery = ProcessQueryExpression(rootQuery, rootClientObject, exp.Expression, false, queryInfo, aggregator);
            aggregator.Member++;
            if (parentQuery == null)
            {
                throw new InvalidQueryExpressionException();
            }
            if (typeof(ClientObject).IsAssignableFrom(exp.Type))
            {
                subQuery = parentQuery.GetSubQuery(exp.Member.Name);
                if (subQuery == null)
                {
                    subQuery = new ClientQueryInternal(null, exp.Member.Name, true, parentQuery);
                    parentQuery.SelectSubQuery(subQuery);
                }
                if (leaf)
                {
                    parentQuery.SelectWithAll(exp.Member.Name);
                }
                return subQuery;
            }
            parentQuery.Select(exp.Member.Name);
            return null;
        }

        private static ClientQueryInternal ProcessMethodCallQueryExpression(ClientQueryInternal rootQuery, ClientObject rootClientObject, MethodCallExpression exp, bool leaf, QueryProcessInfo queryInfo, QueryMethodAggregator aggregator)
        {
            if (exp.Method.IsGenericMethod && ((exp.Method.DeclaringType == typeof(Enumerable)) || (exp.Method.DeclaringType == typeof(Queryable))))
            {
                return ProcessClientQueryableMethodCallQueryExpression(rootQuery, rootClientObject, exp, leaf, queryInfo, aggregator);
            }
            if ((exp.Method.IsGenericMethod && (exp.Method.DeclaringType == typeof(ClientObjectQueryableExtension))) && ((exp.Method.Name == "Include") || (exp.Method.Name == "IncludeWithDefaultProperties")))
            {
                if (exp.Arguments.Count != 2)
                {
                    throw new InvalidQueryExpressionException();
                }
                NewArrayExpression expression = exp.Arguments[1] as NewArrayExpression;
                if (expression == null)
                {
                    throw new InvalidQueryExpressionException();
                }
                ClientQueryInternal internal3 = ProcessQueryExpression(rootQuery, rootClientObject, exp.Arguments[0], false, queryInfo, aggregator);
                aggregator.Include++;
                if (internal3 == null)
                {
                    throw new InvalidQueryExpressionException();
                }
                queryInfo.ByInclude[internal3.ChildItemQuery.Id] = internal3.ChildItemQuery;
                ClientQueryInternal childItemQuery = internal3.ChildItemQuery;
                if (exp.Method.Name == "IncludeWithDefaultProperties")
                {
                    childItemQuery.SelectAllProperties();
                }
                foreach (Expression expression2 in expression.Expressions)
                {
                    LambdaExpression expression4 = ExpressionUtility.StripQuotes(expression2) as LambdaExpression;
                    if (expression4 == null)
                    {
                        throw new InvalidQueryExpressionException();
                    }
                    Expression expression5 = ExpressionUtility.StripConverts(expression4.Body);
                    QueryMethodAggregator aggregator2 = new QueryMethodAggregator(expression2);
                    ProcessQueryExpression(childItemQuery, null, expression5, true, queryInfo, aggregator2);
                    aggregator2.Check();
                }
                return internal3;
            }
            if (!ExpressionUtility.IsGetFieldValueMethod(exp.Method))
            {
                throw new InvalidQueryExpressionException();
            }
            ClientQueryInternal internal5 = ProcessQueryExpression(rootQuery, rootClientObject, exp.Object, false, queryInfo, aggregator);
            aggregator.Member++;
            if (internal5 == null)
            {
                throw new InvalidQueryExpressionException();
            }
            Expression expression6 = exp.Arguments[0];
            expression6 = ExpressionEvaluator.PartialEvaluate(expression6, new Func<Expression, bool>(DataRetrieval.ExpectConstantExpression_CanExpressionBeEvaluated));
            if ((expression6.NodeType != ExpressionType.Constant) || (expression6.Type != typeof(string)))
            {
                throw new InvalidQueryExpressionException();
            }
            ConstantExpression expression7 = (ConstantExpression) expression6;
            internal5.Select((string) expression7.Value);
            return null;
        }

        private static ClientQueryInternal ProcessQueryExpression(ClientQueryInternal rootQuery, ClientObject rootClientObject, Expression exp, bool leaf, QueryProcessInfo queryInfo, QueryMethodAggregator aggregator)
        {
            switch (exp.NodeType)
            {
                case ExpressionType.Call:
                    return ProcessMethodCallQueryExpression(rootQuery, rootClientObject, (MethodCallExpression) exp, leaf, queryInfo, aggregator);

                case ExpressionType.Constant:
                {
                    ConstantExpression expression = (ConstantExpression) exp;
                    if (expression.Value != rootClientObject)
                    {
                        throw new InvalidQueryExpressionException();
                    }
                    return rootQuery;
                }
                case ExpressionType.Convert:
                case ExpressionType.ConvertChecked:
                {
                    UnaryExpression expression2 = (UnaryExpression) exp;
                    return ProcessQueryExpression(rootQuery, rootClientObject, expression2.Operand, leaf, queryInfo, aggregator);
                }
                case ExpressionType.MemberAccess:
                    return ProcessMemberAccessQueryExpression(rootQuery, rootClientObject, (MemberExpression) exp, leaf, queryInfo, aggregator);

                case ExpressionType.Parameter:
                    return rootQuery;
            }
            throw new InvalidQueryExpressionException();
        }

        internal static ClientQueryableResult<T> Retrieve<T>(ClientQueryable<T> clientQueryable) where T: ClientObject
        {
            Expression exp = clientQueryable.Expression;
            ClientQueryableResult<T> result = new ClientQueryableResult<T>();
            ClientObject rootClientObjectForClientQueryableExpression = GetRootClientObjectForClientQueryableExpression(exp);
            if (rootClientObjectForClientQueryableExpression == null)
            {
                throw new InvalidQueryExpressionException();
            }
            ClientQueryInternal query = new ClientQueryInternal(rootClientObjectForClientQueryableExpression, null, false, null);
            rootClientObjectForClientQueryableExpression.Context.AddQueryIdAndResultObject(query.Id, result);
            rootClientObjectForClientQueryableExpression.Context.AddQuery(query);
            QueryProcessInfo queryInfo = new QueryProcessInfo();
            QueryMethodAggregator aggregator = new QueryMethodAggregator(exp);
            ProcessQueryExpression(query, rootClientObjectForClientQueryableExpression, exp, false, queryInfo, aggregator);
            aggregator.Check();
            if (aggregator.Include == 0)
            {
                query.ChildItemQuery.SelectAllProperties();
            }
            queryInfo.FinalProcess();
            return result;
        }

        private class QueryMethodAggregator
        {
            public int Include;
            private Expression m_exp;
            public int Member;
            public int Select;
            public int Take;
            public int Where;

            public QueryMethodAggregator(Expression exp)
            {
                this.m_exp = exp;
            }

            public void Check()
            {
                if (((this.Where > 1) || (this.Include > 1)) || (this.Take > 1))
                {
                    if (this.m_exp == null)
                    {
                        throw new InvalidQueryExpressionException();
                    }
                    throw new InvalidQueryExpressionException(Resources.GetString("NotSupportedQueryExpressionWithExpressionDetail", new object[] { this.m_exp.ToString() }));
                }
            }

            public bool IsEmpty
            {
                get
                {
                    return ((((this.Where == 0) && (this.Include == 0)) && ((this.Take == 0) && (this.Select == 0))) && (this.Member == 0));
                }
            }
        }

        private class QueryProcessInfo
        {
            public Dictionary<long, ClientQueryInternal> ByInclude = new Dictionary<long, ClientQueryInternal>();
            public Dictionary<long, ClientQueryInternal> BySelect = new Dictionary<long, ClientQueryInternal>();
            public Dictionary<ClientQueryInternal, ChunkStringBuilder> Expression = new Dictionary<ClientQueryInternal, ChunkStringBuilder>();

            public void FinalProcess()
            {
                foreach (ClientQueryInternal internal2 in this.BySelect.Values)
                {
                    if (!this.ByInclude.ContainsKey(internal2.Id))
                    {
                        internal2.SelectAllProperties();
                    }
                }
                foreach (KeyValuePair<ClientQueryInternal, ChunkStringBuilder> pair in this.Expression)
                {
                    if (pair.Key.ChildItemFilterSet)
                    {
                        throw new InvalidQueryExpressionException();
                    }
                    pair.Key.ChildItemFilterExpression(pair.Value);
                }
            }
        }
    }
}

